package com.tusion.minio.utils;

import com.google.common.io.ByteStreams;
import com.tusion.minio.config.MinioConfig;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;

@Component
@Slf4j
public class MinioUtil {

    private MinioClient minioClient;

    private String bucketName;

    public MinioUtil() {
        MinioConfig minioConfig = new MinioConfig();
        this.minioClient = minioConfig.getMinioClient();
        this.bucketName = minioConfig.getBucketName();
    }

    public boolean bucketExists() {
        try {
            return this.minioClient.bucketExists((BucketExistsArgs)((BucketExistsArgs.Builder)BucketExistsArgs.builder()
                    .bucket(this.bucketName))
                    .build());
        } catch (Exception e) {
            log.error("{}", this.bucketName, e);
            return false;
        }
    }

    public boolean bucketExists(String bucket) {
        try {
            return this.minioClient.bucketExists((BucketExistsArgs)((BucketExistsArgs.Builder)BucketExistsArgs.builder()
                    .bucket(bucket))
                    .build());
        } catch (Exception e) {
            log.error("{}", bucket, e);
            return false;
        }
    }

    public boolean createBucket() {
        if (bucketExists())
            return true;
        try {
            this.minioClient.makeBucket((MakeBucketArgs)((MakeBucketArgs.Builder)MakeBucketArgs.builder()
                    .bucket(this.bucketName))
                    .build());
            return true;
        } catch (Exception e) {
            log.error("{}", this.bucketName, e);
            return false;
        }
    }

    public boolean createBucket(String bucket) {
        if (bucketExists(bucket))
            return true;
        try {
            this.minioClient.makeBucket((MakeBucketArgs)((MakeBucketArgs.Builder)MakeBucketArgs.builder()
                    .bucket(bucket))
                    .build());
            return true;
        } catch (Exception e) {
            log.error("{}", bucket, e);
            return false;
        }
    }

    public boolean removeBucket() {
        if (!bucketExists())
            return true;
        List<Item> items = getBucketObjects();
        if (items.size() > 0)
            return false;
        try {
            this.minioClient.removeBucket((RemoveBucketArgs)((RemoveBucketArgs.Builder)RemoveBucketArgs.builder()
                    .bucket(this.bucketName))
                    .build());
            return true;
        } catch (Exception e) {
            log.error("删除bucket异常,bucketName={}", bucketName, e);
            return false;
        }
    }

    public String getBucketPolicy() {
        String bucketPolicy = null;
        try {
            bucketPolicy = this.minioClient.getBucketPolicy((GetBucketPolicyArgs)((GetBucketPolicyArgs.Builder)GetBucketPolicyArgs.builder()
                    .bucket(this.bucketName))
                    .build());
        } catch (Exception e) {
            log.error("获取bucket策略异常,bucketName={}", bucketName, e);
        }
        return bucketPolicy;
    }

    public Bucket getBucket() {
        try {
            return this.minioClient.listBuckets()
                    .stream()
                    .filter(b -> b.name().equals(this.bucketName))
                    .findFirst()
                    .orElse(null);
        } catch (Exception e) {
            log.error("获取bucket异常,bucketName={}", bucketName, e);
            return null;
        }
    }

    public List<Bucket> getAllBuckets() {
        try {
            return this.minioClient.listBuckets();
        } catch (Exception e) {
            log.error("获取bucketList异常", e);
            return null;
        }
    }

    public boolean createDirectory(String directoryName) {
        if (!bucketExists())
            createBucket();
        try {
            this.minioClient.putObject((PutObjectArgs)((PutObjectArgs.Builder)((PutObjectArgs.Builder)PutObjectArgs.builder()
                    .bucket(this.bucketName))
                    .object(directoryName))
                    .stream(new ByteArrayInputStream(new byte[0]), 0L, -1L)
                    .build());
            return true;
        } catch (Exception e) {
            log.error("获取bucket目录异常,,bucketName={},directoryName={}", bucketName, directoryName, e);
            return false;
        }
    }

    public boolean objectExist(String objectName) {
        if (!bucketExists())
            return false;
        try {
            this.minioClient.statObject((StatObjectArgs)((StatObjectArgs.Builder)((StatObjectArgs.Builder)StatObjectArgs.builder()
                    .bucket(this.bucketName))
                    .object(objectName))
                    .build());
            return true;
        } catch (Exception e) {
            log.error("获取bucket文件异常,,bucketName={},objectName={}", bucketName, objectName, e);
            return false;
        }
    }

    public boolean folderExist(String folderName) {
        if (!bucketExists())
            return false;
        try {
            Iterable<Result<Item>> results = this.minioClient.listObjects((ListObjectsArgs)((ListObjectsArgs.Builder)ListObjectsArgs.builder()
                    .bucket(this.bucketName))
                    .prefix(folderName)
                    .recursive(false)
                    .build());
            if (results != null)
                for (Result<Item> result : results) {
                    Item item = (Item)result.get();
                    folderName = folderName + "/";
                    if (item.isDir() && folderName.equals(item.objectName()))
                        return true;
                }
        } catch (Exception e) {
            log.error("获取bucket目录异常,,bucketName={},folderName={}", bucketName, folderName, e);
            return false;
        }
        return false;
    }

    public String putObject(MultipartFile multipartFile, String folderName, String aimFileName) {
        if (!bucketExists())
            createBucket();
        if (!StringUtils.hasText(aimFileName))
            aimFileName = UUID.randomUUID().toString();
        String originalFilename = multipartFile.getOriginalFilename();
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        aimFileName = aimFileName + suffix;
        String lastFileName = "";
        if (StringUtils.hasText(folderName)) {
            lastFileName = "/" + folderName + "/" + aimFileName;
        } else {
            lastFileName = aimFileName;
        }
        try (InputStream inputStream = multipartFile.getInputStream()) {
            this.minioClient.putObject((PutObjectArgs)((PutObjectArgs.Builder)((PutObjectArgs.Builder)PutObjectArgs.builder()
                    .bucket(this.bucketName))
                    .object(lastFileName))
                    .stream(inputStream, multipartFile.getSize(), -1L)
                    .contentType(multipartFile.getContentType())
                    .build());
            return getOriginalObjectUrl(lastFileName, null);
        } catch (Exception e) {
            log.error("上传文件异常,bucketName={},folderName={},aimFileName={}", bucketName, folderName, aimFileName, e);
            return null;
        }
    }

    public String putObjectByBucket(MultipartFile multipartFile, String folderName, String aimFileName, String bucket) {
        if (!bucketExists(bucket))
            createBucket(bucket);
        if (!StringUtils.hasText(aimFileName)) {
            aimFileName = UUID.randomUUID().toString();
        }
        String originalFilename = multipartFile.getOriginalFilename();
        String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
        aimFileName = aimFileName + suffix;
        String lastFileName = "";
        if (StringUtils.hasText(folderName)) {
            lastFileName = "/" + folderName + "/" + aimFileName;
        } else {
            lastFileName = aimFileName;
        }
        try (InputStream inputStream = multipartFile.getInputStream()) {
            this.minioClient.putObject((PutObjectArgs)((PutObjectArgs.Builder)((PutObjectArgs.Builder)PutObjectArgs.builder()
                    .bucket(bucket))
                    .object(lastFileName))
                    .stream(inputStream, multipartFile.getSize(), -1L)
                    .contentType(multipartFile.getContentType())
                    .build());
            return getOriginalObjectUrl(lastFileName, bucket);
        } catch (Exception e) {
            log.error("上传文件异常,bucketName={},folderName={},aimFileName={}", bucket, folderName, aimFileName, e);
            return null;
        }
    }

    public String putObject(MultipartFile multipartFile, String fileName) {
        return putObject(multipartFile, null, fileName);
    }

    public String putObject(MultipartFile multipartFile) {
        String filename = null;
        if (org.apache.commons.lang3.StringUtils.isNotBlank(multipartFile.getOriginalFilename())) {
            filename = multipartFile.getOriginalFilename().substring(0, multipartFile.getOriginalFilename().lastIndexOf("."));
        }
        return putObject(multipartFile, null, filename);
    }

    public String putObjectByBucket(MultipartFile multipartFile, String bucket) {
        String filename = null;
        if (org.apache.commons.lang3.StringUtils.isNotBlank(multipartFile.getOriginalFilename())) {
            filename = multipartFile.getOriginalFilename().substring(0, multipartFile.getOriginalFilename().lastIndexOf("."));
        }
        return putObjectByBucket(multipartFile, null, filename, bucket);
    }

    public String putObject(InputStream inputStream, String aimFileName) {
        if (!bucketExists())
            createBucket();
        try {
            PutObjectArgs putObjectArgs = (PutObjectArgs)((PutObjectArgs.Builder)((PutObjectArgs.Builder)PutObjectArgs.builder().bucket(this.bucketName)).object(aimFileName)).stream(inputStream, inputStream.available(), -1L).build();
            this.minioClient.putObject(putObjectArgs);
            inputStream.close();
            return getObjectUrl(aimFileName);
        } catch (Exception e) {
            log.error("上传文件异常,aimFileName={}", aimFileName, e);
            return null;
        }
    }

    public boolean copyObject(String srcBucketName, String srcObjectName, String objectName) {
        try {
            this.minioClient.copyObject(
                    (CopyObjectArgs)((CopyObjectArgs.Builder)((CopyObjectArgs.Builder)CopyObjectArgs.builder()
                            .source((CopySource)((CopySource.Builder)((CopySource.Builder)CopySource.builder()
                                    .bucket(srcBucketName))
                                    .object(srcObjectName))
                                    .build())
                            .bucket(this.bucketName))
                            .object(objectName))
                            .build());
            return true;
        } catch (Exception e) {
            log.error("复制文件异常,bucketName={},srcObjectName={},objectName={}", srcBucketName, srcObjectName, objectName, e);
            return false;
        }
    }

    public void getObject(String fileName, HttpServletResponse response) {
        if (!bucketExists())
            return;
        GetObjectArgs getObjectArgs = (GetObjectArgs)((GetObjectArgs.Builder)((GetObjectArgs.Builder)GetObjectArgs.builder().bucket(this.bucketName)).object(fileName)).build();
        try(ServletOutputStream outputStream = response.getOutputStream();
            GetObjectResponse objectResponse = this.minioClient.getObject(getObjectArgs)) {
            response.setCharacterEncoding("utf-8");
            response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
            ByteStreams.copy((InputStream)objectResponse, (OutputStream)outputStream);
            outputStream.flush();
        } catch (Exception e) {
            log.error("获取文件异常,fileName={}", fileName, e);
        }
    }

    public void getObjectByBucket(String fileName, String bucket, HttpServletResponse response) {
        if (!bucketExists(bucket))
            return;
        GetObjectArgs getObjectArgs = (GetObjectArgs)((GetObjectArgs.Builder)((GetObjectArgs.Builder)GetObjectArgs.builder().bucket(bucket)).object(fileName)).build();
        try(ServletOutputStream outputStream = response.getOutputStream();
            GetObjectResponse objectResponse = this.minioClient.getObject(getObjectArgs)) {
            response.setCharacterEncoding("utf-8");
            response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
            ByteStreams.copy((InputStream)objectResponse, (OutputStream)outputStream);
            outputStream.flush();
        } catch (Exception e) {
            log.error("获取文件异常,fileName={},bucket={}", fileName, bucket, e);
        }
    }

    public InputStream getObject(String objectName) {
        if (!bucketExists())
            return null;
        try {
            StatObjectResponse statObjectResponse = this.minioClient.statObject((StatObjectArgs)((StatObjectArgs.Builder)((StatObjectArgs.Builder)StatObjectArgs.builder()
                    .bucket(this.bucketName))
                    .object(objectName))
                    .build());
            if (statObjectResponse.size() > 0L)
                return (InputStream)this.minioClient.getObject((GetObjectArgs)((GetObjectArgs.Builder)((GetObjectArgs.Builder)GetObjectArgs.builder()
                        .bucket(this.bucketName))
                        .object(objectName))
                        .build());
        } catch (Exception e) {
            log.error("获取文件异常,objectName={}", objectName, e);
        }
        return null;
    }

    public StatObjectResponse getObjectInfo(String objectName) {
        if (!bucketExists())
            return null;
        StatObjectResponse statObjectResponse = null;
        try {
            statObjectResponse = this.minioClient.statObject((StatObjectArgs)((StatObjectArgs.Builder)((StatObjectArgs.Builder)StatObjectArgs.builder()
                    .bucket(this.bucketName))
                    .object(objectName))
                    .build());
        } catch (Exception e) {
            log.error("获取文件异常,objectName={}", objectName, e);
        }
        return statObjectResponse;
    }

    public String getOriginalObjectUrl(String fileName, String bucket) {
        bucket = org.apache.commons.lang3.StringUtils.isBlank(bucket) ? this.bucketName : bucket;
        return MinioConfig.file_url + File.separator + bucket + File.separator + fileName;
    }

    public String getObjectUrl(String fileName) {
        try {
            if (fileName.startsWith("/"))
                fileName = fileName.substring(1);
            GetPresignedObjectUrlArgs build = (GetPresignedObjectUrlArgs)((GetPresignedObjectUrlArgs.Builder)((GetPresignedObjectUrlArgs.Builder)GetPresignedObjectUrlArgs.builder().bucket(this.bucketName)).object(fileName)).method(Method.GET).build();
            return this.minioClient.getPresignedObjectUrl(build);
        } catch (Exception e) {
            log.error("{}", fileName, e);
            return null;
        }
    }

    public String getObjectUrlByBucket(String fileName, String bucket) {
        try {
            if (fileName.startsWith("/"))
                fileName = fileName.substring(1);
            GetPresignedObjectUrlArgs build = (GetPresignedObjectUrlArgs)((GetPresignedObjectUrlArgs.Builder)((GetPresignedObjectUrlArgs.Builder)GetPresignedObjectUrlArgs.builder().bucket(bucket)).object(fileName)).method(Method.GET).build();
            return this.minioClient.getPresignedObjectUrl(build);
        } catch (Exception e) {
            log.error("{},bucket={}", new Object[] { fileName, bucket, e });
            return null;
        }
    }

    public InputStream getObject(String objectName, long offset, long length) {
        if (!bucketExists())
            return null;
        GetObjectResponse objectResponse = null;
        try {
            objectResponse = this.minioClient.getObject((GetObjectArgs)((GetObjectArgs.Builder)((GetObjectArgs.Builder)((GetObjectArgs.Builder)((GetObjectArgs.Builder)GetObjectArgs.builder()
                    .bucket(this.bucketName))
                    .object(objectName))
                    .offset(Long.valueOf(offset)))
                    .length(Long.valueOf(length)))
                    .build());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (InputStream)objectResponse;
    }

    public List<Item> getBucketObjects() {
        if (!bucketExists())
            return null;
        List<Item> items = new ArrayList<>();
        Iterable<Result<Item>> results = this.minioClient.listObjects((ListObjectsArgs)((ListObjectsArgs.Builder)ListObjectsArgs.builder()
                .bucket(this.bucketName))
                .build());
        if (results != null)
            try {
                for (Result<Item> result : results)
                    items.add(result.get());
            } catch (Exception e) {
                log.error("获取文件列表异常,bucketName={}", bucketName, e);
            }
        return items;
    }

    public Iterable<Result<Item>> getObjects(String prefix, boolean recursive) {
        if (!bucketExists())
            return null;
        Iterable<Result<Item>> results = this.minioClient.listObjects((ListObjectsArgs)((ListObjectsArgs.Builder)ListObjectsArgs.builder()
                .bucket(this.bucketName))
                .prefix(prefix)
                .recursive(recursive)
                .build());
        return results;
    }

    public List<Item> getAllObjectsByPrefix(String prefix, boolean recursive) {
        if (!bucketExists())
            return null;
        List<Item> items = new ArrayList<>();
        Iterable<Result<Item>> objectsIterator = this.minioClient.listObjects(
                (ListObjectsArgs)((ListObjectsArgs.Builder)ListObjectsArgs.builder()
                        .bucket(this.bucketName))
                        .prefix(prefix)
                        .recursive(recursive)
                        .build());
        if (objectsIterator != null)
            try {
                for (Result<Item> o : objectsIterator) {
                    Item item = (Item)o.get();
                    items.add(item);
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        return items;
    }

    public boolean removeObject(String fileName) {
        if (!bucketExists())
            return false;
        try {
            this.minioClient.removeObject((RemoveObjectArgs)((RemoveObjectArgs.Builder)((RemoveObjectArgs.Builder)RemoveObjectArgs.builder()
                    .bucket(this.bucketName))
                    .object(fileName))
                    .build());
            return true;
        } catch (Exception e) {
            log.error("删除文件异常,fileName={}", fileName, e);
            return false;
        }
    }

    public boolean removeObjects(String... objects) {
        if (!bucketExists())
            return false;
        List<DeleteObject> deleteObjects = new LinkedList<>();
        Arrays.<String>stream(objects).forEach(s -> deleteObjects.add(new DeleteObject(s)));
        Iterable<Result<DeleteError>> results = this.minioClient.removeObjects((RemoveObjectsArgs)((RemoveObjectsArgs.Builder)RemoveObjectsArgs.builder()
                .bucket(this.bucketName))
                .objects(deleteObjects)
                .build());
        if (results != null)
            try {
                for (Result<DeleteError> result : results) {
                    DeleteError error = (DeleteError)result.get();
                    log.error("Error in deleting object " + error.objectName() + "; " + error.message());
                }
            } catch (Exception e) {
                log.error("批量删除文件异常,fileNames={}", objects, e);
            }
        return true;
    }
}
